/*!
\copyright  Copyright (c) 2023 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Single Linked List API
*/

#include "list_interface.h"
#include "list_debug.h"

#include <stdlib.h>
#include <panic.h>
#include <logging.h>

/**< A linked list node containing a pointer to some 
value/structure and the prev/next node */
typedef struct list_linked_single_node_t
{
    struct list_linked_single_node_t* next;
    size_t element_size;
    unsigned char element[];
} list_linked_single_node_t;

typedef struct list_linked_single_t
{
    list_linked_single_node_t* head;
    list_linked_single_node_t* tail;
    size_t length;
    const list_config_t* config;
} list_linked_single_t;

static list_linked_single_node_t* createNode(list_linked_single_t* list, void *element, size_t size);
static bool ListLinkedSingle_IsEmpty(void* _list);
static void* ListLinkedSingle_Create(const list_config_t* config);
static void ListLinkedSingle_PrependWithSize(void* _list, void* element, size_t size);
static void ListLinkedSingle_AppendWithSize(void* _list, void* element, size_t size);
static void ListLinkedSingle_Prepend(void* _list, void* element);
static void ListLinkedSingle_Append(void* _list, void* element);
static void ListLinkedSingle_DeleteNode(void* _list, void** _node);
static void ListLinkedSingle_Destroy(void** _list);
static void* ListLinkedSingle_GetHead(void* _list);
static void* ListLinkedSingle_GetTail(void* _list);
static void* ListLinkedSingle_GetNext(void* _list, void* _node);
static void* ListLinkedSingle_GetElement(void* _list, void* _node);
static size_t ListLinkedSingle_GetElementSize(void* _list, void* _node);
const list_config_t* ListLinkedSingle_GetConfig(void* _list);

const list_interface_t list_linked_single_if = {
    .ListCreate = ListLinkedSingle_Create,
    .ListDestroy = ListLinkedSingle_Destroy,
    .ListPrependWithSize = ListLinkedSingle_PrependWithSize,
    .ListAppendWithSize = ListLinkedSingle_AppendWithSize,
    .ListPrepend = ListLinkedSingle_Prepend,
    .ListAppend = ListLinkedSingle_Append,
    .ListDeleteNode = ListLinkedSingle_DeleteNode,
    .ListIsEmpty = ListLinkedSingle_IsEmpty,
    .ListGetHead = ListLinkedSingle_GetHead,
    .ListGetTail = ListLinkedSingle_GetTail,
    .ListGetNext = ListLinkedSingle_GetNext,
    .ListGetElement = ListLinkedSingle_GetElement,
    .ListGetElementSize = ListLinkedSingle_GetElementSize,
    .ListGetConfig = ListLinkedSingle_GetConfig,
};

/**
* \brief Creates a node for the list
* \param [in] element: element to store in the node
* \return list_node_t pointer on success
*/
static list_linked_single_node_t* createNode(list_linked_single_t* list, void *element, size_t size)
{
    list_linked_single_node_t* node = PanicNull(calloc(1, sizeof(list_linked_single_node_t) + size));

    node->next = NULL;
    node->element_size = size;

    if (list_store_reference == list->config->element_store.type)
    {
        memcpy(node->element, &element, size);
    }
    else
    {
        memcpy(node->element, element, size);
    }

    return node;
}

static bool ListLinkedSingle_IsEmpty(void* _list)
{
    list_linked_single_t* list = PanicNull(_list);
    return !(list->length);
}

static void* ListLinkedSingle_Create(const list_config_t* config)
{
    list_linked_single_t* list = PanicNull(calloc(1, sizeof(list_linked_single_t)));
    list->config = config;

    return (void*)list;
}

static void ListLinkedSingle_PrependWithSize(void* _list, void* element, size_t size)
{
    list_linked_single_t* list = PanicNull(_list);

    list_linked_single_node_t* node = createNode(list, element, size);

    if (ListLinkedSingle_IsEmpty(list))
    {
        list->head = node;
        list->tail = node;
    }
    else
    {
        node->next = list->head;
        list->head = node;
    }
    list->length++;
}

static void ListLinkedSingle_AppendWithSize(void* _list, void* element, size_t size)
{
    list_linked_single_t* list = PanicNull(_list);
    list_linked_single_node_t* node = createNode(list, element, size);

    if (ListLinkedSingle_IsEmpty(list))
    {
        list->head = node;
        list->tail = node;
    }
    else
    {
        list->tail->next = node;
        list->tail = node;
    }
    list->length++;
}

static size_t listGetElementFixedSize(list_linked_single_t* list)
{
    size_t size = 0;

    if (list->config->element_store.size > 0)
    {
        size = list->config->element_store.size;
    }
    else if (list->config->element_store.type == list_store_reference)
    {
        size = sizeof(void*);
    }
    else
    {
        DEBUG_LOG_PANIC("Unsupported list_store configuration enum:list_store_t:%d", list->config->element_store.type);
    }
    return size;
}

static void ListLinkedSingle_Prepend(void* _list, void* element)
{
    list_linked_single_t* list = PanicNull(_list);

    size_t size = listGetElementFixedSize(list);

    ListLinkedSingle_PrependWithSize(_list, element, size);
}

static void ListLinkedSingle_Append(void* _list, void* element)
{
    list_linked_single_t* list = PanicNull(_list);

    size_t size = listGetElementFixedSize(list);

    ListLinkedSingle_AppendWithSize(_list, element, size);
}

static void ListLinkedSingle_DeleteNode(void* _list, void** _node)
{
    list_linked_single_t* list = PanicNull(_list);
    list_linked_single_node_t* node = PanicNull(*_node);

    list_linked_single_node_t** previous;
    list_linked_single_node_t** current;
    for (current = &list->head, previous = &list->head; *current != NULL; previous = current, current = &(*current)->next)
    {
        if (node == *current)
        {
            *current = (*current)->next;

            if (node == list->tail)
            {
                list->tail = *previous;
            }

            free(node);
            *_node = NULL;
            list->length--;
            break;
        }
    }
}

static void ListLinkedSingle_Destroy(void** _list)
{
    list_linked_single_t* list = PanicNull(*_list);
    list_linked_single_node_t* current;
    for (current = list->head; current != NULL;)
    {
        list_linked_single_node_t* next = current->next;
        free(current);
        current = next;
    }
    free(*_list);
    *_list = NULL;
}

static void* ListLinkedSingle_GetHead(void* _list)
{
    list_linked_single_t* list = PanicNull(_list);

    return list->head;
}

static void* ListLinkedSingle_GetTail(void* _list)
{
    list_linked_single_t* list = PanicNull(_list);

    return list->tail;
}

static void* ListLinkedSingle_GetNext(void* _list, void* _node)
{
    UNUSED(_list);
    list_linked_single_node_t* node = PanicNull(_node);

    return node->next;
}

static void* ListLinkedSingle_GetElement(void* _list, void* _node)
{
    list_linked_single_t* list = PanicNull(_list);
    list_linked_single_node_t* node = PanicNull(_node);

    void* element = NULL;

    if (list_store_reference == list->config->element_store.type)
    {
        element = (void*)(*((uintptr_t*)node->element));
    }
    else
    {
        element = (void*)node->element;
    }

    return element;
}

static size_t ListLinkedSingle_GetElementSize(void* _list, void* _node)
{
    UNUSED(_list);
    list_linked_single_node_t* node = PanicNull(_node);

    return node->element_size;
}
const list_config_t* ListLinkedSingle_GetConfig(void* _list)
{
    list_linked_single_t* list = PanicNull(_list);

    return list->config;
}